﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Reflector
{
    public class MethodAndAttribute<T> where T : Attribute
    {
        public MethodAndAttribute(T Attr, MethodInfo mi) : this(Attr, mi, null) { }
        public MethodAndAttribute(T Attr, MethodInfo mi, object host)
        {
            HostObject = host;
            Method = mi;
            this.Attribute = Attr;
        }
        public object HostObject
        {
            get { return _hostObject; }
            set { _hostObject = value; }
        }
        public MethodInfo Method
        {
            get { return _method; }
            private set { _method = value; }
        }
        public T Attribute
        {
            get { return _attribute; }
            private set { _attribute = value; }
        }
        public void Invoke(params object[] args)
        {
             
            _method.Invoke(_hostObject, args);
        }
        object _hostObject;
        MethodInfo _method;
        T _attribute;
    }
    public class Reflector<T> where T : Attribute
    {
        public static MethodAndAttribute<T>[] Search(object o, Predicate<MethodInfo> predicate)
        {
            Reflector<T> searcher = new Reflector<T>();
            searcher.Add(o, predicate);
            return searcher.GetMethodsAndAttributes();
        }
        public static MethodAndAttribute<T>[] Search(object o)
        {
            return Search(o, MatchAll);
        }
        
        private List<MethodAndAttribute<T>> Methods = new List<MethodAndAttribute<T>>();
        public MethodAndAttribute<T>[] GetMethodsAndAttributes()
        {
            return Methods.ToArray();
        }
        
        private void AddSingle(MethodInfo mi, T attr, object instance)
        {
            MethodAndAttribute<T> maa = new MethodAndAttribute<T>(attr,mi,instance);
            Methods.Add(maa);
        }
        public void Add(object o)
        {
            Add(o, MatchAll);
        }
        private static bool MatchAll(MethodInfo mi)
        {
            return true;
        }
        public void Add(object o, Predicate<MethodInfo> predicate)
        {
            Type targetType = o.GetType();
            foreach (MethodInfo mi in targetType.GetMethods(BindingFlags.NonPublic |
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod
                | BindingFlags.FlattenHierarchy))
            {
                foreach (Attribute attr in Attribute.GetCustomAttributes(mi))
                {
                    Type baseType = attr.GetType();
                    while (baseType != null)
                    {
                        if (baseType == typeof(T))
                        {
                            if (predicate(mi))
                                AddSingle(mi, (T)attr, o);
                            break;
                        }
                        else
                        {
                            if (baseType.BaseType == typeof(object))
                            {
                                baseType = null;
                            }
                            else
                            {
                                baseType = baseType.BaseType;
                            }
                        }
                    }
                }
            }
        }
        public void Clear()
        {
            Methods.Clear();
        }
    }
}
